/*
 * Copyright 2007. Mount Sinai Hospital, Toronto, Canada.
 * 
 * Licensed under the GNU Lesser General Public License, Version 2. You
 * can find a copy of the license at:
 * 
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
 * 
 * IN NO EVENT SHALL MOUNT SINAI HOSPITAL BE LIABLE TO ANY PARTY FOR DIRECT, 
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 
 * PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, 
 * EVEN IF MOUNT SINAI HOSPITAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
 * DAMAGE.
 * 
 * MOUNT SINAI HOSPITAL SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND 
 * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". 
 * MOUNT SINAI HOSPITAL HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, 
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 
 */
package com.sinai.mshab.client.ui.pages;

import java.util.ArrayList;
import java.util.HashMap;

import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sinai.mshab.client.exception.PageNotPopulateableException;
import com.sinai.mshab.client.ui.FormComponent;
import com.sinai.mshab.client.ui.aggregate.FormData;
import com.sinai.mshab.client.util.Util;

/**
 * A Page is a container that represents a page in the forum. This container
 * stores {@link Widget}s and includes forward and back buttons to navigate
 * between different pages. A lot of the code for <code>Page</code> was
 * shamelessly stolen from <a
 * href="http://code.google.com/webtoolkit/documentation/examples/kitchensink/demo.html#Images">
 * Google</a>.
 * 
 * @author Arthur Kalmenson
 */
public abstract class Page extends Composite {

	/**
	 * The vertical panel representing the page.
	 */
	private VerticalPanel page;

	/**
	 * The name of this <code>Page</code>.
	 */
	private String pageName;

	/**
	 * The panel of the page which will contain the content.
	 */
	private VerticalPanel content_panel;

	/**
	 * The top panel.
	 */
	private HorizontalPanel top_panel;

	/**
	 * Bottom panel for next-previous-submit buttons.
	 */
	private DockPanel bottom_panel;

	/**
	 * A boolean that tracks whether a Page subclass has been populated or not.
	 */
	protected boolean instance_exists;

	/**
	 * This Page's container.
	 */
	protected PageContainer container;

	/**
	 * This page's indexability. If set to false, the page does not appear in
	 * the index and does not have an index when shown.
	 */
	private boolean indexable = false;

	/**
	 * Indicates whether this <code>Page</code> was used for user input and
	 * thus needs to appear in {@link ReportPage}.
	 */
	private boolean reportable = false;

	/**
	 * Boolean indicationg whether this page is secure or not.
	 */
	private boolean securePage = false;

	/**
	 * Indicates whether this <code>Page</code> can be populated.
	 */
	private boolean populateable = false;
	
	/**
	 * Keeps track if this page has already been populated.
	 */
	private boolean populated = false;

	/**
	 * Indicates whether this <code>Page</code> is a content form. A content
	 * form is the content gathering form that forms the backbone of the
	 * application.
	 */
	private boolean contentForm = false;

	private Button next_button, prev_button, submit_button;
	
	/**
	 * This variable keeps track of whether we are in admin mode or not.
	 */
	private boolean adminMode = false;
	

	/**
	 * Create an empty <code>Page</code>.
	 */
	public Page(PageContainer page_container) {
		container = page_container;

		page = new VerticalPanel();
		top_panel = new HorizontalPanel();
		bottom_panel = new DockPanel();

		// setting up the top, bottom and content panels.
		top_panel.setVerticalAlignment(DockPanel.ALIGN_MIDDLE);
		top_panel.setStyleName("top_panel");
		bottom_panel.setStyleName("bottom_panel");
		content_panel = new VerticalPanel();

		// adding the panels to the page.
		page.add(top_panel);
		page.add(content_panel);
		page.add(bottom_panel);
		page.setStyleName("page_table");

		this.setIndexable(false);

		initWidget(page);
	}

	/**
	 * To avoid loading all the pages at once, which causes a severe slow down
	 * at initialization, we instead create pages on demand. The
	 * <code>initialize</code> method is called to populate <code>Page</code>
	 * subclasses with their respective widgets. Therefore, widgets are added to
	 * the panel only when required. The <code>Page</code> subclass retains
	 * these widgets and does not need to be populated again.
	 */
	public abstract void initialize();

	/**
	 * Append <code>widget</code> to the current <code>Page</code>.
	 * <code>widget</code> is put at the bottom of the list.
	 * 
	 * @param widget
	 *            a {@link Widget} of any type.
	 */
	public void add(Widget widget) {
		content_panel.add(widget);
	}

	/**
	 * Add <code>widget</code> to the current <code>Page</code> at index
	 * <code>index</code>.
	 * 
	 * @param widget
	 *            a {@link Widget} of any type to add.
	 * @param index
	 *            the index where <code>widget</code> should be added.
	 */
	public void add(Widget widget, int index) {
		content_panel.insert(widget, index);
	}

	/**
	 * Sets the {@link Widget} at index, <code>index</code>, to
	 * <code>widget</code>.
	 * 
	 * @param widget
	 *            the new <code>Widget </code>for <code>index</code>.
	 * @param index
	 *            the index where to put <code>widget</code>.
	 */
	public void set(Widget widget, int index) {
		content_panel.remove(index);
		add(widget, index);
	}

	/**
	 * Returns the next index for another Page level panel. The index is assumed
	 * to come after {@link PageContainer} level panels and after all the
	 * current Page level panels.
	 * 
	 * @param panel
	 *            the panel where we want the next index from.
	 * @return the next index for another Page level panel.
	 */
	private int getNextPagePanelIndex(VerticalSidePanel panel) {
		int result = panel.size();
		return result;
	}

	/**
	 * Adds a next button to the current page.
	 */
	public void addNextButton() {
		addNextButton("Next");
	}

	/**
	 * Adds a next button with text, text, to the current page.
	 * 
	 * @param text
	 *            the text to appear on the next button.
	 */
	public void addNextButton(String text) {
		next_button = new Button(text);
		next_button.addClickListener(new NavigationButtonListener());
		next_button.setTitle("Go to next page");
		next_button.setStyleName("button");
		bottom_panel.add(next_button, DockPanel.EAST);
	}
	
	/**
	 * Removes the next page button from the page.
	 */
	public void removeNextButton() {
		bottom_panel.remove(next_button);
	}

	/**
	 * Adds a previous button to the current page. The default text placed on
	 * the button is "Back".
	 */
	public void addPreviousButton() {
		addPreviousButton("Back");
	}

	/**
	 * Adds a previous button with the text, text, to the current page.
	 * 
	 * @param text
	 *            the text to appear on the back button.
	 */
	public void addPreviousButton(String text) {
		prev_button = new Button(text);
		prev_button.addClickListener(new NavigationButtonListener());
		prev_button.setTitle("Go to the previous page");
		prev_button.setStyleName("button");
		bottom_panel.add(prev_button, DockPanel.WEST);
	}

	/**
	 * Adds a submit button to the current page. The default text placed on the
	 * button is "Submit".
	 */
	public void addSubmitButton() {
		addSubmitButton("Submit");
	}

	/**
	 * Adds a submit button with the text, text, to the current page.
	 * 
	 * @param text
	 *            the text to appear on the submit button.
	 */
	public void addSubmitButton(String text) {
		addSubmitButton(text, new NavigationButtonListener());
	}

	/**
	 * Adds a submit button with the text, text, to the current page. Attach the
	 * {@link ClickListener} listener to the button to provide custom action.
	 * 
	 * @param text
	 *            the text to appear on the submit button.
	 */
	public void addSubmitButton(String text, ClickListener listener) {

		submit_button = new Button(text);
		submit_button.addClickListener(listener);
		submit_button.setTitle("Submit the form");
		submit_button.setStyleName("button");
		bottom_panel.add(submit_button, DockPanel.CENTER);
	}

	/**
	 * Remove the Widget at index <code>index</code>.
	 * 
	 * @param index
	 *            the index of the objec to remove.
	 */
	public void remove(int index) {
		content_panel.remove(index);
	}

	/**
	 * Clears everything in this Page. This involves purging all widgets
	 * contained in this Page.
	 */
	public void clear() {
		content_panel.clear();
		top_panel.clear();
		bottom_panel.clear();
		populated = false;

		setExists(false);
	}

	public boolean validate() {
		return true;
	}

	/**
	 * Tells all widgets on this page to populate themselves with their
	 * registered database table name
	 * 
	 * @throws PageNotPopulateableException
	 */
	public void populate() throws PageNotPopulateableException {
		if (this.isPopulateable() && !populated) {

			beforePopulate();

			for (int i = 0; i < content_panel.getWidgetCount(); i++) {
				if (content_panel.getWidget(i) instanceof FormComponent) {
					FormComponent component = (FormComponent) content_panel
							.getWidget(i);
					component.populate();
				}
			}

			afterPopulate();
			
			// admin mode clean up.
			if (isAdminMode()) {
				adminMode();
			}
			
			populated = true;

		} else {
			throw new PageNotPopulateableException(
					"This page is not populateable");
		}
	}

	/**
	 * This method get's called after a page has been populated It can be
	 * overriden to provide custom behavior
	 */
	public void afterPopulate() {

	}

	/**
	 * This method get's called before a page has been populated It can be
	 * overriden to provide custom behavior
	 */
	public void beforePopulate() {

	}

	/**
	 * This method get's called before another page is loaded. This allows this
	 * page to do any clean up for next page.
	 */
	public void beforeAnotherPageLoaded() {

	}

	/**
	 * This method get's called after FormData retreives all of it's requested
	 * data.
	 */
	public void afterDataAvailable() {

	}

	/**
	 * Tells all subcomponents of <code>Page</code> that are
	 * <code>FormComponents</code> to {@link FormData}.
	 */
	public void submit() {

		beforeSubmit();

		for (int i = 0; i < content_panel.getWidgetCount(); i++) {
			if (content_panel.getWidget(i) instanceof FormComponent) {
				FormComponent component = (FormComponent) content_panel
						.getWidget(i);

				component.sendAnswer(FormData.getInstance());
			}
		}
		// container.showPrintablePage();

		afterSubmit();

	}

	/**
	 * This is a method which get's called after the page is submitted, allowing
	 * the dev to put in custom behavior
	 */
	public void afterSubmit() {

	}

	/**
	 * This is a method which get's called before the page is submitted,
	 * allowing the dev to put in custom behavior
	 */
	public void beforeSubmit() {

	}

	public VerticalPanel toReport() {

		return Util.verticalPanelToPrint(content_panel);
	}

	/**
	 * True if an instance of this <code>Page</code> subclass exists and false
	 * otherwise.
	 * 
	 * @return true if an instance of this <code>Page</code> subclass exists
	 *         and false otherwise.
	 */
	public boolean exists() {
		return instance_exists;
	}

	/**
	 * Return the total number of widgets in this <code>Page</code>.
	 * 
	 * @return the total number of widgets in this <code>Page</code>.
	 */
	public int size() {
		return content_panel.getWidgetCount();
	}

	public boolean isIndexable() {
		return indexable;
	}

	/**
	 * Sets whether this page is indexable or not.
	 * 
	 * @param value
	 *            the new indexablity of the page.
	 */
	public void setIndexable(boolean value) {
		indexable = value;
	}

	/**
	 * When a <code>Page</code> subclass is populated, <code>setExists</code>
	 * sets the <code>instance_exists</code> to true and thus allowing one to
	 * avoid repopulating a page.
	 * 
	 * @param val
	 *            the new value of existance
	 */
	public void setExists(boolean val) {
		instance_exists = val;
	}

	/**
	 * Returns whether this <code>Page</code> was used for user input and thus
	 * needs to appear in {@link ReportPage}.
	 * 
	 * @return true if the <code>Page</code> was used for user input and false
	 *         otherwise.
	 */
	public boolean isReportable() {
		return reportable;
	}

	/**
	 * <code>userInput</code> indicates whether this <code>Page</code> was
	 * used for user input and thus needs to appear in {@link ReportPage}.
	 * 
	 * @param userInput
	 *            whether the <code>Page</code> is a user input page.
	 */
	public void setReportable(boolean userInput) {
		this.reportable = userInput;
	}

	/**
	 * Returns whether this <code>Page</code> can be populated.
	 * 
	 * @return whether this <code>Page</code> can be populated.
	 */
	public boolean isPopulateable() {
		return populateable;
	}

	/**
	 * <code>populateable</code> indicates whether this Page can be populated.
	 * 
	 * @param populateable
	 *            whether the <code>Page</code> can be populated.
	 */
	public void setPopulateable(boolean populateable) {
		this.populateable = populateable;
	}

	/**
	 * Returns the name of this <code>Page</code>.
	 * 
	 * @return the name of this <code>Page</code>.
	 */
	public String getPageName() {
		return pageName;
	}

	/**
	 * Set the name of this <code>Page</code> to <code>pageName</code>.
	 * 
	 * @param pageName
	 *            the new name of this <code>Page</code>.
	 */
	public void setPageName(String pageName) {
		this.pageName = pageName;
	}

	/**
	 * @return the securePage
	 */
	public boolean isSecurePage() {
		return securePage;
	}

	/**
	 * @param securePage
	 *            the securePage to set
	 */
	public void setSecurePage(boolean securePage) {
		this.securePage = securePage;
	}

	/**
	 * Returns whether this Page is a content form. A content form is the
	 * content gathering form that forms the backbone of the application.
	 * 
	 * @return whether this Page is a content form.
	 */
	public boolean isContentForm() {
		return contentForm;
	}

	/**
	 * Sets whether this Page is a content form. A content form is the content
	 * gathering form that forms the backbone of the application.
	 * 
	 * @param contentForm
	 *            whether this Page is a content form.
	 */
	public void setContentForm(boolean contentForm) {
		this.contentForm = contentForm;
	}

	public HorizontalPanel getTopPanel() {
		return top_panel;
	}

	/**
	 * Adds <code>value</code> to the {@link HashMap}, <code>hashMap</code>,
	 * using <code>key</code> as the key.
	 * 
	 * @param key
	 *            the value to be added to the HashMap.
	 * @param value
	 *            the key to the HashMap.
	 */
	public static HashMap addToHashMap(String key, FormComponent value,
			HashMap hashMap) {

		HashMap result = new HashMap(hashMap);

		// if the key already exists...
		if (result.containsKey(key)) {

			// append question to it the value of the key.
			ArrayList composites = (ArrayList) result.get(key);
			composites.add(value);
			result.put(key, composites);
		} else {

			// if the key isn't in the HashMap yet, we need to create a new
			// ArrayList to store the QuestionComposite and add it to the
			// HashMap
			ArrayList composites = new ArrayList();
			composites.add(value);

			result.put(key, composites);
		}

		return result;
	}

	/**
	 * @param adminMode the adminMode to set
	 */
	public void setAdminMode(boolean adminMode) {
		this.adminMode = adminMode;
	}

	/**
	 * @return the adminMode
	 */
	public boolean isAdminMode() {
		return adminMode;
	}
	
	/**
	 * This method gives the page a chance to modify itself when loaded in admin mode.
	 */
	public void adminMode() {
		
	}

	/**
	 * A {@link ClickListener} that is attached to navigation buttons. When one
	 * of the buttons is clicked, the appropriate action is taken (i.e. the next
	 * or previous pages are loaded, or the form is submitted).
	 * 
	 * @author Arthur Kalmenson
	 */
	private class NavigationButtonListener implements ClickListener {
		public void onClick(Widget sender) {
			if (sender == prev_button) {
				container.previous();

			} else if (sender == next_button && validate()) {
				container.next();
				Util.putPageToTop();
			} else if (sender == submit_button) {
				container.submitAll();
				container.reset();
				container.loadPage("Thank You");
			}
		}
	}
}
